package top.v5it.japi.plus.log;

import cn.hutool.core.date.StopWatch;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.multipart.MultipartFile;
import top.v5it.japi.plus.common.util.JackSonUtil;
import top.v5it.japi.plus.common.util.TypeUtil;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

import static top.v5it.japi.plus.common.ConstantCommon.LOG_DIVIDING_LINE;


/**
 * 操作日志使用spring event异步入库
 *
 * @author zhanpu
 */
@Slf4j
@Aspect
@Lazy
public class JapiOperLogAspect {

    /**
     * 用时超时时间
     */
    private static final long TIMEOUT = 3000L;

    /**
     * 操作日志服务
     */
    private final OperLogService operLogService;

    /**
     * 记录执行时间
     */
    private final InheritableThreadLocal<Long> time;

    /**
     * 构造函数
     *
     * @param operLogService 操作日志服务
     */
    public JapiOperLogAspect(OperLogService operLogService) {
        this.operLogService = operLogService;
        this.time = new InheritableThreadLocal<Long>() {

            @Override
            protected Long initialValue() {
                return 0L;
            }
        };
    }

    /**
     * 定义切入点，以带有{@link JapiOperLog}注解的方法作为切入点
     */
    @Pointcut("@annotation(top.v5it.japi.plus.log.JapiOperLog)")
    public void logPointCut() {
    }

    @SneakyThrows
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) {
        String className = point.getTarget().getClass().getName();
        String methodName = point.getSignature().getName();
        String threadName = Thread.currentThread().getName();
        String id = StrUtil.join("#", className, methodName, threadName);

        // 记录业务操作用时
        StopWatch stopWatch = new StopWatch(id, false);
        stopWatch.start();
        Object obj = point.proceed();
        stopWatch.stop();

        final long executeTime = stopWatch.getTotalTimeMillis();
        this.time.set(executeTime);

        if (executeTime > TIMEOUT) {
            log.warn(stopWatch.prettyPrint());
        }

        return obj;
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "logPointCut()", returning = "rtv")
    public void doAfterReturning(JoinPoint joinPoint, Object rtv) {

        if (log.isDebugEnabled()) {
            log.debug("{}返回结果\n{}{}", LOG_DIVIDING_LINE, rtv, LOG_DIVIDING_LINE);
        }

        handleLog(joinPoint, rtv, null);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, null, e);
    }

    /**
     * 日志处理
     *
     * @param joinPoint 切点
     * @param rtv       正常时返回结果
     * @param e         异常时异常信息
     */
    protected void handleLog(final JoinPoint joinPoint, Object rtv, final Exception e) {

        getAnnotation(joinPoint)
                .ifPresent(v -> {

                    // 异常信息
                    String errorMsg = Objects.nonNull(e) ? e.getMessage() : "";

                    String rtvParam = null;

                    if (Objects.nonNull(rtv)) {
                        try {
                            rtvParam = JackSonUtil.getMapper().writeValueAsString(rtv);
                        } catch (Exception ex) {
                            // ignore

                            log.error(ex.getMessage(), ex);
                        }
                    }

                    try {
                        String parameters = getParameters(joinPoint).orElse("");

                        operLogService.addOperLog(
                                OperLogUtil.builder(errorMsg)
                                        .setExecuteTime(time.get())
                                        .setOperParam(parameters)
                                        .setRtvParam(rtvParam)
                                        .setTitle(v.value())
                        );
                    } catch (Exception ex) {
                        // ignore

                        log.error(ex.getMessage(), ex);
                    } finally {
                        time.remove();
                    }

                });

    }

    /**
     * 是否存在注解，如果存在就获取
     *
     * @param joinPoint 切点
     */
    private Optional<JapiOperLog> getAnnotation(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        return Optional
                .ofNullable(method)
                .filter(v -> v.isAnnotationPresent(JapiOperLog.class))
                .map(v -> v.getAnnotation(JapiOperLog.class));
    }

    /**
     * 获取参数
     *
     * @param joinPoint
     * @return
     */
    private Optional<String> getParameters(JoinPoint joinPoint) {

        Object[] args = joinPoint.getArgs();

        if (Arrays.asList(args).isEmpty()) {
            return Optional.of("无参请求");
        }

        Map<String, Object> params = new HashMap<>();
        final int length = args.length;

        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        Parameter[] parameters = method.getParameters();

        for (int i = 0; i < length; i++) {
            Parameter parameter = parameters[i];
            Class<?> parameterType = parameter.getType();
            String name = parameter.getName();

            if (TypeUtil.isMultipartFile(parameterType)) {
                MultipartFile file = (MultipartFile) args[i];
                params.put(name, file.getOriginalFilename());
            } else if (TypeUtil.isHttpServletRequest(parameterType) || TypeUtil.isHttpServletResponse(parameterType)) {
            } else if (TypeUtil.isBasicType(parameterType) || TypeUtil.isString(parameterType)) {
                params.put(name, args[i]);
            } else {
                params.put(name, args[i]);
            }
        }

        if (!params.isEmpty()) {
            try {
                String value = JackSonUtil.getMapper().writeValueAsString(params);
                return Optional.of(value);
            } catch (JsonProcessingException ex) {
                // ignore

                log.error(ex.getMessage(), ex);
                log.error("参数信息：{}", params);
            }
        }

        return Optional.of("");
    }

}
